/*
 * (C) Copyright 2007-2011
 * Allwinner Technology Co., Ltd. <www.allwinnertech.com>
 * Aaron <leafy.myeh@allwinnertech.com>
 *
 * MMC driver for allwinner sunxi platform.
 *
 * See file CREDITS for list of people who contributed to this
 * project.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 * MA 02111-1307 USA
 */
#include <common.h>
#include <asm/io.h>
#include <asm/arch/platform.h>
#include <asm/arch/clock.h>
#include <asm/arch/ccmu.h>
#include <asm/arch/mmc.h>
#include <asm/arch/timer.h>
#include <malloc.h>
#include <mmc.h>
#include <sys_config.h>
#include <libfdt.h>
#include <fdt_support.h>
#include "../mmc_def.h"
#include "../sunxi_mmc.h"
#include "sunxi_host0_2_v4p1.h"
#include "sunxi_host_mmc.h"

extern char *spd_name[];
struct sunxi_mmc_host mmc_host[3];
struct mmc_reg_v4p1 mmc_host_reg_bak[3];

static u8 ext_odly_spd_freq[MAX_SPD_MD_NUM*MAX_CLK_FREQ_NUM];
static u8 ext_sdly_spd_freq[MAX_SPD_MD_NUM*MAX_CLK_FREQ_NUM];
static u8 ext_odly_spd_freq_sdc0[MAX_SPD_MD_NUM*MAX_CLK_FREQ_NUM];
static u8 ext_sdly_spd_freq_sdc0[MAX_SPD_MD_NUM*MAX_CLK_FREQ_NUM];

static int mmc_clear_timing_para(int sdc_no)
{
	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
	int i, j;

	for (i = 0; i < MAX_SPD_MD_NUM; i++) {
		for (j = 0; j < MAX_CLK_FREQ_NUM; j++) {
			mmchost->tm0.odly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm0.sdly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm0.def_odly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm0.def_sdly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm1.odly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm1.sdly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm1.def_odly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm1.def_sdly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm3.odly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm3.sdly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm3.def_odly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm3.def_sdly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm4.odly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm4.sdly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm4.def_odly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
			mmchost->tm4.def_sdly[i * MAX_CLK_FREQ_NUM + j] = 0xFF;
		}
	}

	for (j = 0; j < MAX_CLK_FREQ_NUM; j++) {
		mmchost->tm4.dsdly[j] = 0xFF;
		mmchost->tm4.def_dsdly[j] = 0xFF;
	}

	return 0;
}

static int mmc_init_default_timing_para(int sdc_no)
{
	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];

	if (sdc_no == 0) {
		/* timing mode 1 */
		mmchost->tm0.cur_spd_md = DS26_SDR12;
		mmchost->tm0.cur_freq = CLK_400K;
		mmchost->tm0.sample_point_cnt = MMC_CLK_SAMPLE_POINIT_MODE_0;

		mmchost->tm0
		    .def_odly[DS26_SDR12 * MAX_CLK_FREQ_NUM + CLK_400K] = 0;
		mmchost->tm0
		    .def_sdly[DS26_SDR12 * MAX_CLK_FREQ_NUM + CLK_400K] = 0;
		mmchost->tm0.def_odly[DS26_SDR12 * MAX_CLK_FREQ_NUM + CLK_25M] =
		    3;
		mmchost->tm0.def_sdly[DS26_SDR12 * MAX_CLK_FREQ_NUM + CLK_25M] =
		    5;

		mmchost->tm0
		    .def_odly[HSSDR52_SDR25 * MAX_CLK_FREQ_NUM + CLK_25M] = 3;
		mmchost->tm0
		    .def_sdly[HSSDR52_SDR25 * MAX_CLK_FREQ_NUM + CLK_25M] = 5;
		mmchost->tm0
		    .def_odly[HSSDR52_SDR25 * MAX_CLK_FREQ_NUM + CLK_50M] = 3;
		mmchost->tm0
		    .def_sdly[HSSDR52_SDR25 * MAX_CLK_FREQ_NUM + CLK_50M] = 5;

		mmchost->tm0
		    .def_odly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_25M] = 3;
		mmchost->tm0
		    .def_sdly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_25M] = 5;
		mmchost->tm0
		    .def_odly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_50M] = 3;
		mmchost->tm0
		    .def_sdly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_50M] = 5;

	} else if (sdc_no == 2) {

		/* timing mode 1 */
		mmchost->tm1.cur_spd_md = DS26_SDR12;
		mmchost->tm1.cur_freq = CLK_400K;
		mmchost->tm1.sample_point_cnt = MMC_CLK_SAMPLE_POINIT_MODE_1;

		mmchost->tm1
		    .def_odly[DS26_SDR12 * MAX_CLK_FREQ_NUM + CLK_400K] = 0;
		mmchost->tm1
		    .def_sdly[DS26_SDR12 * MAX_CLK_FREQ_NUM + CLK_400K] = 0;
		mmchost->tm1.def_odly[DS26_SDR12 * MAX_CLK_FREQ_NUM + CLK_25M] =
		    0;
		mmchost->tm1.def_sdly[DS26_SDR12 * MAX_CLK_FREQ_NUM + CLK_25M] =
		    0;

		mmchost->tm1
		    .def_odly[HSSDR52_SDR25 * MAX_CLK_FREQ_NUM + CLK_25M] = 1;
		mmchost->tm1
		    .def_sdly[HSSDR52_SDR25 * MAX_CLK_FREQ_NUM + CLK_25M] = 1;
		mmchost->tm1
		    .def_odly[HSSDR52_SDR25 * MAX_CLK_FREQ_NUM + CLK_50M] = 1;
		mmchost->tm1
		    .def_sdly[HSSDR52_SDR25 * MAX_CLK_FREQ_NUM + CLK_50M] = 1;

		mmchost->tm1
		    .def_odly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_25M] = 1;
		mmchost->tm1
		    .def_sdly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_25M] = 1;
		mmchost->tm1
		    .def_odly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_50M] = 1;
		mmchost->tm1
		    .def_sdly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_50M] = 1;
	}

#if 0
	printf("0x%x 0x%x 0x%x 0x%x\n", (u32)mmchost->tm4.def_odly, (u32)mmchost->tm4.def_sdly, (u32)mmchost->tm4.sdly, (u32)mmchost->tm4.odly);
	for (j = 0; j < MAX_SPD_MD_NUM; j++) {
		for (i = 0; i < MAX_CLK_FREQ_NUM; i++) {
			printf("%02x ", mmchost->tm4.def_odly[j*MAX_CLK_FREQ_NUM + i]);
		}
		printf("\n");
	}
	printf("\n\n");


	for (j = 0; j < MAX_SPD_MD_NUM; j++) {
		for (i = 0; i < MAX_CLK_FREQ_NUM; i++) {
			printf("%02x ", mmchost->tm4.def_sdly[j*MAX_CLK_FREQ_NUM + i]);
		}
		printf("\n");
	}
	printf("\n");
#endif

	return 0;
}


static int mmc_get_sdly_auto_sample(int sdc_no)
{

#if 0
	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
	u32 *p = (u32 *)&mmchost->cfg.platform_caps.sdly.tm4_smx_fx[0];
	int spd_md, f;
	u32 val;
	int work_mode = uboot_spare_head.boot_data.work_mode;

	if (sdc_no != 2) {
		MMCINFO("don't support auto sample\n");
		return -1;
	}

	/* sdly is invalid */
	if (work_mode != WORK_MODE_BOOT)
		return 0;

#if 0
	for (f = 0; f < 5; f++)
		MMCINFO("0x%x 0x%x\n", p[f*2 + 0], p[f*2 + 1]);
#endif
	for (spd_md = 0; spd_md < MAX_SPD_MD_NUM; spd_md++) {
		if (spd_md == HS400) {
			val = p[spd_md*2 + 0];
			for (f = 0; f < 4; f++) {
				mmchost->tm4.dsdly[f] = (val>>(f*8)) & 0xFF;
				/* MMCINFO("dsdly-0 0x%x\n", mmchost->tm4.dsdly[f]); */
			}

			val = p[spd_md*2 + 1];
			for (f = 4; f < MAX_CLK_FREQ_NUM; f++) {
				mmchost->tm4.dsdly[f] = (val>>((f-4)*8)) & 0xFF;
				/* MMCINFO("dsdly-1 0x%x\n", mmchost->tm4.dsdly[f]); */
			}
		} else {
			val = p[spd_md*2 + 0];
			for (f = 0; f < 4; f++) {
				mmchost->tm4.sdly[spd_md*MAX_CLK_FREQ_NUM + f] = (val>>(f*8)) & 0xFF;
				/* MMCINFO("sdly-0 0x%x\n", mmchost->tm4.sdly[spd_md*MAX_CLK_FREQ_NUM + f]); */
			}

			val = p[spd_md*2 + 1];
			for (f = 4; f < MAX_CLK_FREQ_NUM; f++) {
				mmchost->tm4.sdly[spd_md*MAX_CLK_FREQ_NUM + f] = (val>>((f-4)*8)) & 0xFF;
				/* MMCINFO("sdly-1 0x%x\n", mmchost->tm4.sdly[spd_md*MAX_CLK_FREQ_NUM + f]); */
			}
		}

	}

	return 0;
#else
	MMCINFO("don't support auto sample\n");
	return -1;
#endif
}

static int mmc_get_dly_manual_sample(int sdc_no)
{
	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
	struct mmc_config *cfg = &mmchost->cfg;
	int imd, ifreq;

	if (sdc_no == 2) {
		if (mmchost->timing_mode == SUNXI_MMC_TIMING_MODE_1) {
			struct sunxi_mmc_timing_mode1 *p = &mmchost->tm1;

			for (imd = 0; imd < MAX_SPD_MD_NUM; imd++) {
				for (ifreq = 0; ifreq < MAX_CLK_FREQ_NUM;
				     ifreq++) {
					/* MMCINFO("%d %d\n",
					 * cfg->platform_caps.odly_spd_freq[imd*MAX_CLK_FREQ_NUM
					 * + ifreq],
					 * cfg->platform_caps.sdly_spd_freq[imd*MAX_CLK_FREQ_NUM
					 * + ifreq]); */
					p->odly[imd * MAX_CLK_FREQ_NUM +
						ifreq] =
					    cfg->platform_caps.odly_spd_freq
						[imd * MAX_CLK_FREQ_NUM +
						 ifreq];
					p->sdly[imd * MAX_CLK_FREQ_NUM +
						ifreq] =
					    cfg->platform_caps.sdly_spd_freq
						[imd * MAX_CLK_FREQ_NUM +
						 ifreq];
				}
				/* MMCINFO("\n"); */
			}
		} else {
			MMCINFO("sdc %d timing mode %d error --1\n", sdc_no,
				mmchost->timing_mode);
			return -1;
		}
	} else if (sdc_no == 0) {
		if (mmchost->timing_mode == SUNXI_MMC_TIMING_MODE_0) {
			struct sunxi_mmc_timing_mode0 *p = &mmchost->tm0;

			for (imd = 0; imd < MAX_SPD_MD_NUM; imd++) {
				for (ifreq = 0; ifreq < MAX_CLK_FREQ_NUM;
				     ifreq++) {
					/* MMCINFO("%d %d\n",
					 * cfg->platform_caps.odly_spd_freq[imd*MAX_CLK_FREQ_NUM
					 * + ifreq],
					 * cfg->platform_caps.sdly_spd_freq[imd*MAX_CLK_FREQ_NUM
					 * + ifreq]); */
					p->odly[imd * MAX_CLK_FREQ_NUM +
						ifreq] =
					    cfg->platform_caps.odly_spd_freq
						[imd * MAX_CLK_FREQ_NUM +
						 ifreq];
					p->sdly[imd * MAX_CLK_FREQ_NUM +
						ifreq] =
					    cfg->platform_caps.sdly_spd_freq
						[imd * MAX_CLK_FREQ_NUM +
						 ifreq];
				}
				/* MMCINFO("\n"); */
			}
		} else {
			MMCINFO("sdc %d timing mode %d error --2\n", sdc_no,
				mmchost->timing_mode);
			return -1;
		}

	} else {
		MMCINFO("mmc_get_dly_manual_sample: error sdc_no %d\n", sdc_no);
		return -1;
	}

	return 0;
}

static int mmc_update_timing_para(int sdc_no)
{
	int ret = 0;
	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
	struct mmc_platform_caps *p = &mmchost->cfg.platform_caps;

	if (p->sample_mode == AUTO_SAMPLE_MODE) {
		ret = mmc_get_sdly_auto_sample(sdc_no);
		if (ret) {
			MMCINFO("update auto sample timing para fail!\n");
		}
	} else if (p->sample_mode == MAUNAL_SAMPLE_MODE) {
		ret = mmc_get_dly_manual_sample(sdc_no);
		if (ret) {
			MMCINFO("update manual sample timing para fail!\n");
		}
	} else {
		MMCINFO("use default timing para!\n");
		mmchost->tm1.odly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_50M] =
		    mmchost->tm1
			.def_odly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_50M];
		mmchost->tm1.sdly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_50M] =
		    mmchost->tm1
			.def_sdly[HSDDR52_DDR50 * MAX_CLK_FREQ_NUM + CLK_50M];
	}

	return ret;
}


static void mmc_get_para_from_fex(int sdc_no)
{
	int rval, ret = 0;
	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
	struct mmc_config *cfg = &mmchost->cfg;
	int nodeoffset = 0;
	int i, imd, ifreq;
	char ctmp[30];

	if (sdc_no == 0) {
		nodeoffset =
		    fdt_path_offset(working_fdt, FDT_PATH_CARD0_BOOT_PARA);
		if (nodeoffset < 0) {
			MMCINFO("get card0 para fail\n");
			return;
		}
		if (fdt_set_all_pin(FDT_PATH_CARD0_BOOT_PARA, "pinctrl-0")) {
			MMCINFO("set card0 pin fail\n");
			return;
		}
		/* set gpio */
		/* gpio_request_simple("card0_boot_para", NULL); */

		/* ret = script_parser_fetch("card0_boot_para", "sdc_wipe",
		 * &rval, 1); */
		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_wipe",
				      (uint32_t *)(&rval));
		if (ret < 0)
			MMCDBG("get sdc_wipe fail.\n");
		else {
			if (rval & DRV_PARA_DISABLE_SECURE_WIPE) {
				MMCINFO(
				    "disable driver secure wipe operation.\n");
				cfg->platform_caps.drv_wipe_feature |=
				    DRV_PARA_DISABLE_SECURE_WIPE;
			}
			if (rval & DRV_PARA_DISABLE_EMMC_SANITIZE) {
				MMCINFO("disable emmc sanitize feature.\n");
				cfg->platform_caps.drv_wipe_feature |=
				    DRV_PARA_DISABLE_EMMC_SANITIZE;
			}
			if (rval & DRV_PARA_DISABLE_EMMC_SECURE_PURGE) {
				MMCINFO("disable emmc secure purge feature.\n");
				cfg->platform_caps.drv_wipe_feature |=
				    DRV_PARA_DISABLE_EMMC_SECURE_PURGE;
			}
			if (rval & DRV_PARA_DISABLE_EMMC_TRIM) {
				MMCINFO("disable emmc trim feature.\n");
				cfg->platform_caps.drv_wipe_feature |=
				    DRV_PARA_DISABLE_EMMC_TRIM;
			}
		}

		/* ret = script_parser_fetch("card0_boot_para", "sdc_erase",
		 * &rval, 1); */
		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_erase",
				      (uint32_t *)(&rval));
		if (ret < 0)
			MMCDBG("get sdc0 sdc_erase fail.\n");
		else {
			if (rval & DRV_PARA_DISABLE_EMMC_ERASE) {
				MMCINFO("disable emmc erase.\n");
				cfg->platform_caps.drv_erase_feature |=
				    DRV_PARA_DISABLE_EMMC_ERASE;
			}
			if (rval & DRV_PARA_ENABLE_EMMC_SANITIZE_WHEN_ERASE) {
				MMCINFO("enable emmc sanitize when erase.\n");
				cfg->platform_caps.drv_erase_feature |=
				    DRV_PARA_ENABLE_EMMC_SANITIZE_WHEN_ERASE;
			}
		}

		/* ret = script_parser_fetch("card0_boot_para", "sdc_boot",
		 * &rval, 1); */
		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_boot",
				      (uint32_t *)(&rval));
		if (ret < 0)
			MMCDBG("get sdc0 sdc_boot fail.\n");
		else {
			if (rval & DRV_PARA_NOT_BURN_USER_PART) {
				MMCINFO("don't burn boot0 to user part.\n");
				cfg->platform_caps.drv_burn_boot_pos |=
				    DRV_PARA_NOT_BURN_USER_PART;
			}
			if (rval & DRV_PARA_BURN_EMMC_BOOT_PART) {
				MMCINFO("burn boot0 to emmc boot part.\n");
				cfg->platform_caps.drv_burn_boot_pos |=
				    DRV_PARA_BURN_EMMC_BOOT_PART;
			}
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_odly_50M",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc0 sdc_odly_50M fail.\n");
			cfg->platform_caps.boot_odly_50M = 0xff;
		} else {
			MMCINFO("get sdc0 sdc_odly_50M %d.\n", rval);
			cfg->platform_caps.boot_odly_50M = rval;
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_sdly_50M",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc0 sdc_sdly_50M fail.\n");
			cfg->platform_caps.boot_sdly_50M = 0xff;
		} else {
			MMCINFO("get sdc0 sdc_sdly_50M %d.\n", rval);
			cfg->platform_caps.boot_sdly_50M = rval;
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_odly_50M_ddr", (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc0 sdc_odly_50M_ddr fail.\n");
			cfg->platform_caps.boot_odly_50M_ddr = 0xff;
		} else {
			MMCINFO("get sdc0 sdc_odly_50M_ddr %d.\n", rval);
			cfg->platform_caps.boot_odly_50M_ddr = rval;
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_sdly_50M_ddr", (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc0 sdc_sdly_50M_ddr fail.\n");
			cfg->platform_caps.boot_sdly_50M_ddr = 0xff;
		} else {
			MMCINFO("get sdc0 sdc_sdly_50M_ddr %d.\n", rval);
			cfg->platform_caps.boot_sdly_50M_ddr = rval;
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_freq",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc0 sdc_freq fail.\n");
			cfg->platform_caps.boot_hs_f_max = 0x0;
		} else {
			if (rval >= 50)
				cfg->platform_caps.boot_hs_f_max = 50;
			else
				cfg->platform_caps.boot_hs_f_max = rval;
			MMCINFO("get sdc0 sdc_freq %d.%d\n", rval,
				cfg->platform_caps.boot_hs_f_max);
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_b0p",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc0 sdc_b0p fail.\n");
			cfg->platform_caps.boot0_para = 0x0;
		} else {
			MMCINFO("get sdc0 sdc_b0p %d.\n", rval);
			cfg->platform_caps.boot0_para = rval;
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_ex_dly_used", (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get card0_boot_para:sdc_ex_dly_used fail\n");
		} else {
			if (rval == 1) { /* maual sample point from fex */
				cfg->platform_caps.sample_mode =
				    MAUNAL_SAMPLE_MODE;
				MMCINFO("get sdc_ex_dly_used %d, use manual "
					"sdly in fex\n",
					rval);
			} else {
				cfg->platform_caps.sample_mode = 0x0;
				MMCINFO("undefined value %d, use default dly\n",
					rval);
			}
		}

		if (cfg->platform_caps.sample_mode == MAUNAL_SAMPLE_MODE) {
			for (imd = 0; imd < MAX_SPD_MD_NUM; imd++) {
				for (ifreq = 0; ifreq < MAX_CLK_FREQ_NUM;
				     ifreq++) {
					sprintf(ctmp, "sdc_odly_%d_%d", imd,
						ifreq);
					ret = fdt_getprop_u32(
					    working_fdt, nodeoffset, ctmp,
					    (uint32_t *)(&rval));
					if (ret < 0) {
						MMCDBG("get sdc0 %s fail.\n",
						       ctmp);
						cfg->platform_caps.odly_spd_freq
						    [imd * MAX_CLK_FREQ_NUM +
						     ifreq] = 0xff;
					} else {
						cfg->platform_caps.odly_spd_freq
						    [imd * MAX_CLK_FREQ_NUM +
						     ifreq] = rval & 0xff;
						MMCINFO(
						    "get sdc0 %s 0x%x for "
						    "%d-%s %d.\n",
						    ctmp,
						    cfg->platform_caps.odly_spd_freq
							[imd *
							     MAX_CLK_FREQ_NUM +
							 ifreq],
						    imd, spd_name[imd], ifreq);
					}

					sprintf(ctmp, "sdc_sdly_%d_%d", imd,
						ifreq);
					ret = fdt_getprop_u32(
					    working_fdt, nodeoffset, ctmp,
					    (uint32_t *)(&rval));
					if (ret < 0) {
						MMCDBG("get sdc0 %s fail.\n",
						       ctmp);
						cfg->platform_caps.sdly_spd_freq
						    [imd * MAX_CLK_FREQ_NUM +
						     ifreq] = 0xff;
					} else {
						cfg->platform_caps.sdly_spd_freq
						    [imd * MAX_CLK_FREQ_NUM +
						     ifreq] = rval & 0xff;
						MMCINFO(
						    "get sdc0 %s 0x%x for "
						    "%d-%s %d.\n",
						    ctmp,
						    cfg->platform_caps.sdly_spd_freq
							[imd *
							     MAX_CLK_FREQ_NUM +
							 ifreq],
						    imd, spd_name[imd], ifreq);
					}
				}
			}
		}

	} else if (sdc_no == 2) {

		nodeoffset =
		    fdt_path_offset(working_fdt, FDT_PATH_CARD2_BOOT_PARA);
		if (nodeoffset < 0) {
			MMCINFO("get card0 para fail\n");
			return;
		}
		if (fdt_set_all_pin(FDT_PATH_CARD2_BOOT_PARA, "pinctrl-0")) {
			MMCINFO("set card0 pin fail\n");
			return;
		}
		/* gpio_request_simple("card2_boot_para", NULL); */
		/* MMCINFO("=====~~~~~~~~~~~~0x%x 0x%x 0x%x\n", *(volatile u32
		 * *)(0x1c20848), *(volatile u32 *)(0x1c2084C), *(volatile u32
		 * *)(0x1c20850)); */

		/* ret = script_parser_fetch("card2_boot_para", "sdc_wipe",
		 * &rval, 1); */
		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_wipe",
				      (uint32_t *)(&rval));
		if (ret < 0)
			MMCDBG("get sdc_wipe fail.\n");
		else {
			if (rval & DRV_PARA_DISABLE_SECURE_WIPE) {
				MMCINFO(
				    "disable driver secure wipe operation.\n");
				cfg->platform_caps.drv_wipe_feature |=
				    DRV_PARA_DISABLE_SECURE_WIPE;
			}
			if (rval & DRV_PARA_DISABLE_EMMC_SANITIZE) {
				MMCINFO("disable emmc sanitize feature.\n");
				cfg->platform_caps.drv_wipe_feature |=
				    DRV_PARA_DISABLE_EMMC_SANITIZE;
			}
			if (rval & DRV_PARA_DISABLE_EMMC_SECURE_PURGE) {
				MMCINFO("disable emmc secure purge feature.\n");
				cfg->platform_caps.drv_wipe_feature |=
				    DRV_PARA_DISABLE_EMMC_SECURE_PURGE;
			}
			if (rval & DRV_PARA_DISABLE_EMMC_TRIM) {
				MMCINFO("disable emmc trim feature.\n");
				cfg->platform_caps.drv_wipe_feature |=
				    DRV_PARA_DISABLE_EMMC_TRIM;
			}
		}

		/* ret = script_parser_fetch("card2_boot_para", "sdc_erase",
		 * &rval, 1); */
		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_erase",
				      (uint32_t *)(&rval));
		if (ret < 0)
			MMCDBG("get sdc2 sdc_erase fail.\n");
		else {
			if (rval & DRV_PARA_DISABLE_EMMC_ERASE) {
				MMCINFO("disable emmc erase.\n");
				cfg->platform_caps.drv_erase_feature |=
				    DRV_PARA_DISABLE_EMMC_ERASE;
			}
			if (rval & DRV_PARA_ENABLE_EMMC_SANITIZE_WHEN_ERASE) {
				MMCINFO("enable emmc sanitize when erase.\n");
				cfg->platform_caps.drv_erase_feature |=
				    DRV_PARA_ENABLE_EMMC_SANITIZE_WHEN_ERASE;
			}
		}

		/* ret = script_parser_fetch("card2_boot_para", "sdc_boot",
		 * &rval, 1); */
		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_boot",
				      (uint32_t *)(&rval));
		if (ret < 0)
			MMCDBG("get sdc2 sdc_boot fail.\n");
		else {
			if (rval & DRV_PARA_NOT_BURN_USER_PART) {
				MMCINFO("don't burn boot0 to user part.\n");
				cfg->platform_caps.drv_burn_boot_pos |=
				    DRV_PARA_NOT_BURN_USER_PART;
			}
			if (rval & DRV_PARA_BURN_EMMC_BOOT_PART) {
				MMCINFO("burn boot0 to emmc boot part.\n");
				cfg->platform_caps.drv_burn_boot_pos |=
				    DRV_PARA_BURN_EMMC_BOOT_PART;
			}
		}

		/* ret = script_parser_fetch("card2_boot_para",
		 * "sdc_ex_dly_used", &rval, 1); */
		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_ex_dly_used", (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get card2_boot_para:sdc_ex_dly_used fail\n");

			/* fill invalid sample config */
			memset(
			    mmchost->cfg.platform_caps.sdly.tm4_smx_fx, 0xff,
			    sizeof(mmchost->cfg.platform_caps.sdly.tm4_smx_fx));
		}
		/* ret1 = script_parser_fetch("mmc2_para", "sdc_ex_dly_used",
		 * &rval_ker, 1); */
		/* if (ret1 < 0) */
		/* 	MMCDBG("get mmc2_para:sdc_ex_dly_used fail\n"); */
		if (!(ret < 0) /* && !(ret1 < 0)*/) {
			int work_mode = uboot_spare_head.boot_data.work_mode;
			struct boot_sdmmc_private_info_t *priv_info =
			    (struct boot_sdmmc_private_info_t
				 *)(uboot_spare_head.boot_data
					.sdcard_spare_data);
			struct tune_sdly *sdly = &(priv_info->tune_sdly);
			u32 *p = (u32 *)&mmchost->cfg.platform_caps.sdly
				     .tm4_smx_fx[0];

			if (/*(rval_ker == 2) &&*/ (rval ==
						    2)) { /* only when kernal
							     use auto
							     sample,uboot will
							     use auto sample. */
				cfg->platform_caps.sample_mode =
				    AUTO_SAMPLE_MODE;
				MMCINFO("get sdc_ex_dly_used %d, use auto "
					"tuning sdly\n",
					rval);
				if (work_mode != WORK_MODE_BOOT) {
					/*usb product will auto get sample
					 * point, no need to get auto sdly, so
					 * first used default value*/
					MMCINFO("current is product mode, it "
						"will tune sdly later\n");
				} else {
					/* copy sample point cfg from uboot
					 * header to internal variable */
					if (sdly != NULL)
						memcpy(
						    p, sdly,
						    sizeof(struct tune_sdly));
					else
						MMCINFO("get sdly from uboot "
							"header fail\n");
				}
			} else if (rval ==
				   1) { /* maual sample point from fex */
				cfg->platform_caps.sample_mode =
				    MAUNAL_SAMPLE_MODE;
				MMCINFO("get sdc_ex_dly_used %d, use manual "
					"sdly in fex\n",
					rval);
			} else {
				cfg->platform_caps.sample_mode = 0x0;
				MMCINFO("undefined value %d, use default dly\n",
					rval);
			}
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_force_boot_tuning",
				      (uint32_t *)(&rval));
		if (ret < 0)
			MMCDBG(
			    "get card2_boot_para:sdc_force_boot_tuning fail\n");
		else {
			if (rval == 1) {
				MMCINFO("card2 force to execute tuning during "
					"boot.\n");
				cfg->platform_caps.force_boot_tuning = 1;
			} else {
				MMCDBG("card2 don't force to execute tuning "
				       "during boot.\n");
				cfg->platform_caps.force_boot_tuning = 0;
			}
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_io_1v8",
				      (uint32_t *)(&rval));
		if (ret < 0)
			MMCDBG("get card2_boot_para:sdc_io_1v8 fail\n");
		else {
			if (rval == 1) {
				MMCINFO("card2 io is 1.8V.\n");
				cfg->platform_caps.io_is_1v8 = 1;
			} else {
				MMCDBG("card2 io is 3V.\n");
			}
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_odly_50M",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_odly_50M fail.\n");
			cfg->platform_caps.boot_odly_50M = 0xff;
		} else {
			MMCINFO("get sdc2 sdc_odly_50M %d.\n", rval);
			cfg->platform_caps.boot_odly_50M = rval;
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_sdly_50M",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_sdly_50M fail.\n");
			cfg->platform_caps.boot_sdly_50M = 0xff;
		} else {
			MMCINFO("get sdc2 sdc_sdly_50M %d.\n", rval);
			cfg->platform_caps.boot_sdly_50M = rval;
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_odly_50M_ddr", (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_odly_50M_ddr fail.\n");
			cfg->platform_caps.boot_odly_50M_ddr = 0xff;
		} else {
			MMCINFO("get sdc2 sdc_odly_50M_ddr %d.\n", rval);
			cfg->platform_caps.boot_odly_50M_ddr = rval;
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_sdly_50M_ddr", (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_sdly_50M_ddr fail.\n");
			cfg->platform_caps.boot_sdly_50M_ddr = 0xff;
		} else {
			MMCINFO("get sdc2 sdc_sdly_50M_ddr %d.\n", rval);
			cfg->platform_caps.boot_sdly_50M_ddr = rval;
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_freq",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_freq fail.\n");
			cfg->platform_caps.boot_hs_f_max = 0x0;
		} else {
			if (rval >= 50)
				cfg->platform_caps.boot_hs_f_max = 50;
			else
				cfg->platform_caps.boot_hs_f_max = rval;
			MMCINFO("get sdc2 sdc_freq %d.%d\n", rval,
				cfg->platform_caps.boot_hs_f_max);
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_b0p",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_b0p fail.\n");
			cfg->platform_caps.boot0_para = 0x0;
		} else {
			cfg->platform_caps.boot0_para = rval;
			MMCINFO("get sdc2 sdc_b0p %d.\n",
				cfg->platform_caps.boot0_para);
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset, "sdc_tm4_win_th",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_tm4_win_th fail.\n");
			cfg->platform_caps.tm4_timing_window_th = 12;
		} else {
			if ((rval < 4) || (rval > 64))
				cfg->platform_caps.tm4_timing_window_th = 12;
			else
				cfg->platform_caps.tm4_timing_window_th = rval;
			MMCINFO("get sdc2 sdc_tm4_win_th %d.\n",
				cfg->platform_caps.tm4_timing_window_th);
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_tm4_r_cycle", (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_tm4_r_cycle fail.\n");
			cfg->platform_caps.tm4_tune_r_cycle = 0x0;
		} else {
			if ((rval < 1) || (rval > 40))
				cfg->platform_caps.tm4_tune_r_cycle = 15;
			else
				cfg->platform_caps.tm4_tune_r_cycle = rval;
			MMCINFO("get sdc2 sdc_tm4_r_cycle %d.\n",
				cfg->platform_caps.tm4_tune_r_cycle);
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_tm4_hs200_max_freq",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_tm4_hs200_max_freq fail.\n");
			cfg->platform_caps.tm4_tune_hs200_max_freq = 0x0;
		} else {
			if ((rval < 50) || (rval > 200))
				cfg->platform_caps.tm4_tune_hs200_max_freq =
				    0x0;
			else
				cfg->platform_caps.tm4_tune_hs200_max_freq =
				    rval;
			MMCINFO("get sdc2 sdc_tm4_hs200_max_freq %d.\n",
				cfg->platform_caps.tm4_tune_hs200_max_freq);
		}

		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_tm4_hs400_max_freq",
				      (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_tm4_hs400_max_freq fail.\n");
			cfg->platform_caps.tm4_tune_hs400_max_freq = 0x0;
		} else {
			if ((rval < 50) || (rval > 200))
				cfg->platform_caps.tm4_tune_hs400_max_freq =
				    0x0;
			else
				cfg->platform_caps.tm4_tune_hs400_max_freq =
				    rval;
			MMCINFO("get sdc2 sdc_tm4_hs400_max_freq %d.\n",
				cfg->platform_caps.tm4_tune_hs400_max_freq);
		}

		for (i = 0; i < MAX_EXT_FREQ_POINT_NUM; i++) {
			sprintf(ctmp, "sdc_tm4_ext_freq_%d", i);
			ret = fdt_getprop_u32(working_fdt, nodeoffset, ctmp,
					      (uint32_t *)(&rval));
			if (ret < 0) {
				MMCDBG("get sdc2 %s fail.\n", ctmp);
				cfg->platform_caps.tm4_tune_ext_freq[i] = 0x0;
			} else {
				MMCINFO("get sdc2 %s 0x%x.\n", ctmp, rval);
				if ((rval & 0xff) >= 25)
					cfg->platform_caps
					    .tm4_tune_ext_freq[i] = rval;
				else {
					cfg->platform_caps
					    .tm4_tune_ext_freq[i] = 0x0;
					MMCINFO("invalid freq %d MHz, discard "
						"this ext freq point\n",
						(rval & 0xff));
				}
			}
		}

		if (cfg->platform_caps.sample_mode == MAUNAL_SAMPLE_MODE) {
			for (imd = 0; imd < MAX_SPD_MD_NUM; imd++) {
				for (ifreq = 0; ifreq < MAX_CLK_FREQ_NUM;
				     ifreq++) {
					sprintf(ctmp, "sdc_odly_%d_%d", imd,
						ifreq);
					ret = fdt_getprop_u32(
					    working_fdt, nodeoffset, ctmp,
					    (uint32_t *)(&rval));
					if (ret < 0) {
						MMCDBG("get sdc2 %s fail.\n",
						       ctmp);
						cfg->platform_caps.odly_spd_freq
						    [imd * MAX_CLK_FREQ_NUM +
						     ifreq] = 0x0;
					} else {
						cfg->platform_caps.odly_spd_freq
						    [imd * MAX_CLK_FREQ_NUM +
						     ifreq] = rval & 0x1;
						MMCINFO(
						    "get sdc2 %s 0x%x for "
						    "%d-%s %d.\n",
						    ctmp,
						    cfg->platform_caps.odly_spd_freq
							[imd *
							     MAX_CLK_FREQ_NUM +
							 ifreq],
						    imd, spd_name[imd], ifreq);
					}

					sprintf(ctmp, "sdc_sdly_%d_%d", imd,
						ifreq);
					ret = fdt_getprop_u32(
					    working_fdt, nodeoffset, ctmp,
					    (uint32_t *)(&rval));
					if (ret < 0) {
						MMCDBG("get sdc2 %s fail.\n",
						       ctmp);
						cfg->platform_caps.sdly_spd_freq
						    [imd * MAX_CLK_FREQ_NUM +
						     ifreq] = 0xff;
					} else {
						cfg->platform_caps.sdly_spd_freq
						    [imd * MAX_CLK_FREQ_NUM +
						     ifreq] = rval & 0xff;
						MMCINFO(
						    "get sdc2 %s 0x%x for "
						    "%d-%s %d.\n",
						    ctmp,
						    cfg->platform_caps.sdly_spd_freq
							[imd *
							     MAX_CLK_FREQ_NUM +
							 ifreq],
						    imd, spd_name[imd], ifreq);
					}
				}
			}
		}

		/* speed mode caps */
		ret = fdt_getprop_u32(working_fdt, nodeoffset,
				      "sdc_dis_host_caps", (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_dis_host_caps fail.\n");
			cfg->platform_caps.host_caps_mask = 0x0;
		} else {
			cfg->platform_caps.host_caps_mask = rval;
			MMCINFO("get sdc2 sdc_dis_host_caps 0x%x.\n",
				cfg->platform_caps.host_caps_mask);
		}

		ret =
		    fdt_getprop_u32(working_fdt, nodeoffset,
				    "sdc_kernel_no_limit", (uint32_t *)(&rval));
		if (ret < 0) {
			MMCDBG("get sdc2 sdc_kernel_no_limit fail.\n");
			cfg->platform_caps.tune_limit_kernel_timing = 0x1;
		} else {
			if (rval == 1)
				cfg->platform_caps.tune_limit_kernel_timing =
				    0x0;
			else
				cfg->platform_caps.tune_limit_kernel_timing =
				    0x1;
			MMCINFO(
			    "get sdc2 sdc_kernel_no_limit 0x%x, limit 0x%x.\n",
			    rval, cfg->platform_caps.tune_limit_kernel_timing);
		}

		/* fmax, fmax_ddr */
	} else {
		MMCINFO("%s: input sdc_no error: %d\n", __FUNCTION__, sdc_no);
	}

	return ;
}

static void mmc_get_para_from_dtb(int sdc_no)
{
	int ret = 0;
	/* struct sunxi_mmc_host* mmchost = &mmc_host[sdc_no]; */
	/* struct mmc_config *cfg = &mmchost->cfg; */
	int nodeoffset;
	char prop_path[128] = {0};
	/* u32 prop_val = 0; */

	if (sdc_no == 0) {
		strcpy(prop_path, "mmc0");
		nodeoffset = fdt_path_offset(working_fdt, prop_path);
		if (nodeoffset < 0) {
			MMCINFO("can't find node \"%s\",will add new node\n",
				prop_path);
			goto __ERRRO_END;
		}
	} else {
	}

	return ;

__ERRRO_END:
	printf ("fdt err returned %s\n", fdt_strerror(ret));
	return ;
}

#define	 MMC_REG_FIFO_OS	(0x200)

static int mmc_resource_init(int sdc_no)
{
	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];
	MMCDBG("init mmc %d resource\n", sdc_no);
	switch (sdc_no) {
	case 0:
		mmchost->reg = (struct sunxi_mmc *)SUNXI_SMHC0_BASE;
		mmchost->database = SUNXI_SMHC0_BASE + MMC_REG_FIFO_OS;
		mmchost->mclkbase = CCMU_SDMMC0_CLK_REG;
		break;
	case 1:
		mmchost->reg = (struct sunxi_mmc *)SUNXI_SMHC1_BASE;
		mmchost->database = SUNXI_SMHC1_BASE + MMC_REG_FIFO_OS;
		mmchost->mclkbase = CCMU_SDMMC1_CLK_REG;
		break;
	case 2:
		mmchost->reg = (struct sunxi_mmc *)SUNXI_SMHC2_BASE;
		mmchost->database = SUNXI_SMHC2_BASE + MMC_REG_FIFO_OS;
		mmchost->mclkbase = CCMU_SDMMC2_CLK_REG;
		break;
	default:
		MMCINFO("Wrong mmc number %d\n", sdc_no);
		break;
	}

	mmchost->hclkbase = CCMU_BUS_CLK_GATING_REG0;
	mmchost->hclkrst  = CCM_AHB1_RST_REG0;

	mmchost->mmc_no = sdc_no;

	return 0;
}

int mmc_clk_io_onoff(int sdc_no, int onoff, int reset_clk)
{
	int rval;
	struct sunxi_mmc_host *mmchost = &mmc_host[sdc_no];

	/* config ahb clock */
	if (onoff) {
		rval = readl(mmchost->hclkrst);
		rval |= (1 << (8 + sdc_no));
		writel(rval, mmchost->hclkrst);
		rval = readl(mmchost->hclkbase);
		rval |= (1 << (8 + sdc_no));
		writel(rval, mmchost->hclkbase);

		rval = readl(mmchost->mclkbase);
		rval |= (1U << 31);
		writel(rval, mmchost->mclkbase);
	} else {

		rval = readl(mmchost->mclkbase);
		rval &= ~(1U << 31);
		writel(rval, mmchost->mclkbase);

		rval = readl(mmchost->hclkbase);
		rval &= ~(1 << (8 + sdc_no));
		writel(rval, mmchost->hclkbase);

		rval = readl(mmchost->hclkrst);
		rval &= ~(1 << (8 + sdc_no));
		writel(rval, mmchost->hclkrst);
	}

	/* config mod clock */
	if (reset_clk) {
		rval = readl(mmchost->mclkbase);
		/*set to 24M default value*/
		rval &= ~(0x7fffffff);
		writel(rval, mmchost->mclkbase);
		mmchost->mod_clk = 24000000;
	}

	dumphex32("ccmu", (char *)SUNXI_CCM_BASE, 0x100);
	dumphex32("gpio", (char *)SUNXI_PIO_BASE, 0x100);
	dumphex32("mmc", (char *)mmchost->reg, 0x100);

	return 0;
}

int sunxi_mmc_init(int sdc_no)
{
	struct sunxi_mmc_host *host = NULL;

	MMCINFO("mmc driver ver %s\n", DRIVER_VER);

	if ((sdc_no != 2) && (sdc_no != 0)) {
		MMCINFO("sdc_on error, %d\n", sdc_no);
		return -1;
	}

	memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host));
	host = &mmc_host[sdc_no];
	memset(&mmc_host_reg_bak[sdc_no], 0, sizeof(struct mmc_reg_v4p1));
	host->reg_bak =  &mmc_host_reg_bak[sdc_no];

	if ((sdc_no == 2)) {
		host->cfg.platform_caps.odly_spd_freq = &ext_odly_spd_freq[0];
		host->cfg.platform_caps.sdly_spd_freq = &ext_sdly_spd_freq[0];
	} else if (sdc_no == 0) {
		host->cfg.platform_caps.odly_spd_freq = &ext_odly_spd_freq_sdc0[0];
		host->cfg.platform_caps.sdly_spd_freq = &ext_sdly_spd_freq_sdc0[0];
	}

	host->cfg.name = "SUNXI SD/MMC";
	host->cfg.host_no = sdc_no;
	host->cfg.ops = &sunxi_mmc_ops;

	host->cfg.voltages = MMC_VDD_32_33 | MMC_VDD_33_34
		| MMC_VDD_27_28 | MMC_VDD_28_29 | MMC_VDD_29_30
		| MMC_VDD_30_31 | MMC_VDD_31_32 | MMC_VDD_34_35
		| MMC_VDD_35_36;

	host->cfg.b_max = CONFIG_SYS_MMC_MAX_BLK_COUNT;

	if (sdc_no == 0) {
		host->cfg.f_min = 400000;
#ifdef FPGA_PLATFORM
		host->cfg.f_max = 12000000;
#else
		host->cfg.f_max = 50000000;
#endif
	} else if ((sdc_no == 2)) {
		host->cfg.f_min = 400000;
#ifdef FPGA_PLATFORM
		host->cfg.f_max = 12000000;
#else
		host->cfg.f_max = 50000000;
#endif
	}

	if ((sdc_no == 0) || (sdc_no == 1))
		host->timing_mode = SUNXI_MMC_TIMING_MODE_0;
	else if ((sdc_no == 2))
		host->timing_mode = SUNXI_MMC_TIMING_MODE_1;

	host->pdes = malloc(256 * 1024);
	if (host->pdes == NULL) {
		MMCINFO("%s: get mem for descriptor failed\n", __FUNCTION__);
		return -1;
	}

	mmc_clear_timing_para(sdc_no);
	mmc_init_default_timing_para(sdc_no);
	mmc_get_para_from_fex(sdc_no);
	mmc_get_para_from_dtb(sdc_no);
	mmc_update_timing_para(sdc_no);
	mmc_resource_init(sdc_no);
	mmc_clk_io_onoff(sdc_no, 1, 1);

	host->cfg.host_caps = MMC_MODE_4BIT | MMC_MODE_HS | MMC_MODE_HC \
							| MMC_MODE_HS_52MHz;
	if (host->cfg.host_no == 2)
		host->cfg.host_caps |= MMC_MODE_DDR_52MHz | MMC_MODE_8BIT;


	if (host->cfg.platform_caps.host_caps_mask) {
		u32 mask = host->cfg.platform_caps.host_caps_mask;
		if (mask & DRV_PARA_DISABLE_MMC_MODE_HS400)
			host->cfg.host_caps &= (~DRV_PARA_DISABLE_MMC_MODE_HS400);
		if (mask & DRV_PARA_DISABLE_MMC_MODE_HS200)
			host->cfg.host_caps &= (~(DRV_PARA_DISABLE_MMC_MODE_HS200
										| DRV_PARA_DISABLE_MMC_MODE_HS400));
		if (mask & DRV_PARA_DISABLE_MMC_MODE_DDR_52MHz)
			host->cfg.host_caps &= (~(DRV_PARA_DISABLE_MMC_MODE_DDR_52MHz
										| DRV_PARA_DISABLE_MMC_MODE_HS400
										| DRV_PARA_DISABLE_MMC_MODE_HS200));
		if (mask & DRV_PARA_DISABLE_MMC_MODE_HS_52MHz)
			host->cfg.host_caps &= (~(DRV_PARA_DISABLE_MMC_MODE_HS_52MHz
										| DRV_PARA_DISABLE_MMC_MODE_DDR_52MHz
										| DRV_PARA_DISABLE_MMC_MODE_HS400
										| DRV_PARA_DISABLE_MMC_MODE_HS200));
		if (mask & DRV_PARA_DISABLE_MMC_MODE_8BIT)
			host->cfg.host_caps &= (~(DRV_PARA_DISABLE_MMC_MODE_8BIT
										| DRV_PARA_DISABLE_MMC_MODE_HS400));
	}
	MMCDBG("mmc->host_caps %x\n", host->cfg.host_caps);

	host->mmc = mmc_create(&host->cfg, host);
	if (host->mmc == NULL) {
		MMCINFO("%s: register mmc %d failed\n", __FUNCTION__, sdc_no);
		return -1;
	} else
		MMCDBG("%s: register mmc %d ok\n", __FUNCTION__, __LINE__);

	return 0;
}

int sunxi_mmc_exit(int sdc_no)
{
	mmc_clk_io_onoff(sdc_no, 0, 1);
	memset(&mmc_host[sdc_no], 0, sizeof(struct sunxi_mmc_host));
	memset(&mmc_host_reg_bak[sdc_no], 0, sizeof(struct mmc_reg_v4p1));
	MMCDBG("sunxi mmc%d exit\n", sdc_no);
	return 0;
}
